package controlador;

import com.thoughtworks.xstream.XStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import modelo.excepciones.ExcepcionDeTanque;
import modelo.servicios.InformacionBasica;
import modelo.servicios.Tanque;
import org.apache.log4j.Logger;

/**
 * Clase que accede a los niveles de petróleo del tanque
 * @author Eduardo
 */
public class Informacion {

    private static boolean estadoDesbordamientoTanque1 = false;
    private static boolean estadoDesbordamientoTanque2 = false;
    private static String mensajeColapsoTanque1 = "";
    private static String mensajeColapsoTanque2 = "";
    private static Logger logger = Logger.getLogger(Informacion.class);
    private static String rutaInformacionBasica = "C:/Users/Eduardo/Documents/Sistemas Distribuidos"
            + "/Practica/Tanques petroleros/TanquesPetroleros/src/configuracion/"
            + "InformacionBasica.xml";

    /**
     * Permite almacenar el documento XML recibido por socket para luego hacer el mapeo desde XML a
     * Java
     * @param xmlRecibido Documento XML que es recibido a través del socket
     * @throws ExcepcionDeTanque Excepción de formato del documento XML recibido
     */
    public void almacenarXMLrecibido(String xmlRecibido)
            throws ExcepcionDeTanque {
        InformacionBasica ib = obtenerInformacionBasica();

        Tanque tanqueActual = null;
        Tanque tanqueNuevo = null;

        try {
            BufferedReader lector = new BufferedReader(new FileReader(ib.getRuta()));
            BufferedWriter editor = null;

            float llegadaLts = 0;

            /**
             * Si el archivo en la ruta está vacío, se escribe la información requerida
             */
            if (lector.readLine() == null) {
                /**
                 * Habilitando buffer de escritura
                 */
                editor = new BufferedWriter(new FileWriter(ib.getRuta()));

                /**
                 * Escribiendo la información
                 */
                xmlRecibido = "<object-stream>\n" + xmlRecibido + "\n</object-stream>";
                editor.write(xmlRecibido);

                /**
                 * Deshabilitando buffer de escritura
                 */
                editor.flush();
                editor.close();

                /**
                 * Invocando al método que consulta el nivel actual del tanque
                 */
                tanqueActual = consultarNivelActual();
                
                DecimalFormatSymbols dfs = new DecimalFormatSymbols();
                dfs.setDecimalSeparator('.');
                DecimalFormat df = new DecimalFormat("#.##", dfs);

                tanqueActual.setLts(Float.parseFloat(df.format(tanqueActual.getLts())));

                /**
                 * Si la inyección de petróleo no es dirigida al tanque cuyo id es "idTanque"
                 */
                if (tanqueActual.getId() != ib.getIdTanque()) {
                    vaciarInformacionTanque();
                    ExcepcionDeTanque ext = new ExcepcionDeTanque(1, ib.getIdTanque());
                    logger.error(ext.getMensaje());
                    System.out.println("El tanque " + ib.getIdTanque() + " contiene una cantidad "
                            + "actual de " + tanqueActual.getLts() + " litros de petróleo");
                    System.out.println(ext.getMensaje());
                    throw ext;
                }

                /**
                 * Si el documento XML contiene cantidades negativas de petróleo
                 */
                if (tanqueActual.getLts() <= 0) {
                    vaciarInformacionTanque();
                    ExcepcionDeTanque ext = new ExcepcionDeTanque(2, ib.getIdTanque());
                    logger.error(ext.getMensaje());
                    System.out.println("El tanque " + ib.getIdTanque() + " contiene una cantidad "
                            + "actual de " + tanqueActual.getLts() + " litros de petróleo");
                    System.out.println(ext.getMensaje());
                    throw ext;
                }

                llegadaLts = tanqueActual.getLts();
            }

            /**
             * Si el archivo no está vacío, se actualiza la información que contiene después de
             * escribir la información que acaba de llegar en un archivo temporal "rutaTemp"
             */
            else {
                /**
                 * Habilitando buffer de escritura
                 */
                editor = new BufferedWriter(new FileWriter(ib.getRutaTemp()));

                /**
                 * Escribiendo la información
                 */
                xmlRecibido = "<object-stream>\n" + xmlRecibido + "\n</object-stream>";
                editor.write(xmlRecibido);

                /**
                 * Deshabilitando buffer de escritura
                 */
                editor.flush();
                editor.close();

                /**
                 * Invocando al método que consulta el nivel actual del tanque
                 */
                tanqueActual = consultarNivelActual();

                DecimalFormatSymbols dfs = new DecimalFormatSymbols();
                dfs.setDecimalSeparator('.');
                DecimalFormat df = new DecimalFormat("#.##", dfs);

                tanqueActual.setLts(Float.parseFloat(df.format(tanqueActual.getLts())));

                /**
                 * Invocando al método que consulta los datos de la nueva entrada del tanque
                 */
                tanqueNuevo = consultarNuevaEntrada();

                tanqueNuevo.setLts(Float.parseFloat(df.format(tanqueNuevo.getLts())));

                /**
                 * Si la inyección de petróleo no es dirigida al tanque cuyo id es "idTanque"
                 */
                if (tanqueNuevo.getId() != ib.getIdTanque()) {
                    ExcepcionDeTanque ext = new ExcepcionDeTanque(1, ib.getIdTanque());
                    logger.error(ext.getMensaje());
                    System.out.println("El tanque " + ib.getIdTanque() + " contiene una cantidad "
                            + "actual de " + tanqueActual.getLts() + " litros de petróleo");
                    System.out.println(ext.getMensaje());
                    throw ext;
                }

                /**
                 * Si el documento XML contiene cantidades negativas de petróleo
                 */
                else if (tanqueNuevo.getLts() <= 0) {
                    ExcepcionDeTanque ext = new ExcepcionDeTanque(2, ib.getIdTanque());
                    logger.error(ext.getMensaje());
                    System.out.println("El tanque " + ib.getIdTanque() + " contiene una cantidad "
                            + "actual de " + tanqueActual.getLts() + " litros de petróleo");
                    System.out.println(ext.getMensaje());
                    throw ext;
                }

                /**
                 * Si no se cumple alguna de las condiciones anteriores se actualiza la información
                 * del tanque
                 */
                else {
                    /**
                     * Actualizando la información del tanque
                     */
                    tanqueActual.setLts(tanqueActual.getLts() + tanqueNuevo.getLts());

                    /**
                     * Invocando al método que actualiza el nivel de petróleo en el tanque
                     */
                    actualizarNivel(tanqueActual);
                }

                llegadaLts = tanqueNuevo.getLts();
            }

            logger.info("Llego entrada de petroleo en el tanque " + ib.getIdTanque() + " por la "
                    + "cantidad de " + llegadaLts + " litros");

            System.out.println("Llegó entrada de petróleo en el tanque " + ib.getIdTanque() + " por"
                    + " la cantidad de " + llegadaLts + " litros");

            if (tanqueActual.getLts() <= ib.getCapacidadTanque() * 0.7)  {
                logger.info("El tanque " + ib.getIdTanque() + " contiene una cantidad actual de " +
                        tanqueActual.getLts() + " litros de petroleo");

                System.out.println("El tanque " + ib.getIdTanque() + " contiene una cantidad actual"
                        + " de " + tanqueActual.getLts() + " litros de petróleo");
            }

            /**
             * Instanciando un objeto de la clase que se encarga de controlar el flujo de
             * petróleo entre los tanques petroleros
             */
            FlujoPetroleo flujo = new FlujoPetroleo();
            flujo.direccionarPetroleo(tanqueActual, llegadaLts);
                       
       } catch (IOException ex) {
           logger.error("Ocurrio un error de entrada/salida de datos");
           System.out.println("Ocurrió un error de entrada/salida de datos");
       } catch (NullPointerException npe) {
           System.out.println("El tanque 3 contiene una cantidad actual de " + tanqueActual.getLts()
                   + " litros de petróleo");
           logger.error("El documento XML esta mal estructurado");
           System.out.println("El documento XML está mal estructurado");
       }
    }

    /**
     * Método que consulta el nivel actual del tanque
     * @return Devuelve un objeto de la clase Tanque con la información actual del tanque
     */
    public Tanque consultarNivelActual() {
        InformacionBasica ib = obtenerInformacionBasica();

        Tanque tanqueActual = null;

        try {
            /**
             * Instanciando un objeto de la clase que permite realizar el mapeo de XML a Java
             */
            XStream xstream = new XStream();

            /**
             * Declarando un flujo de entrada de datos por archivo para obtener la información
             * actual del tanque
             */
            FileInputStream informacionTanque = new FileInputStream(ib.getRuta());

            ObjectInputStream infoT = xstream.createObjectInputStream(informacionTanque);

            xstream.alias("tanque", Tanque.class);

            /**
             * Realizando el mapeo de un documento XML a un objeto Java
             */
            tanqueActual = (Tanque)infoT.readObject();

            infoT.close();
        } catch (IOException ex) {
            logger.error("Ocurrio un error de entrada/salida de datos");
            System.out.println("Ocurrió un error de entrada/salida de datos");
        } catch (ClassNotFoundException ex) {
            logger.error("Ocurrio un error de Clase No Encontrada");
            System.out.println("Ocurrió un error de Clase No Encontrada");
        } finally {
            return tanqueActual;
        }
    }

    /**
     * Método que consulta la información de la nueva entrada en el tanque
     * @return Devuelve un objeto de la clase Tanque con la información de la nueva entrada en el
     * tanque
     */
    private Tanque consultarNuevaEntrada() {
        InformacionBasica ib = obtenerInformacionBasica();

        Tanque tanqueNuevo = null;

        try {
            /**
             * Instanciando un objeto de la clase que permite realizar el mapeo de XML a Java
             */
            XStream xstream = new XStream();

            /**
             * Declarando un flujo de entrada de datos por archivo para obtener la información
             * acerca de la nueva entrada en el tanque
             */
            FileInputStream informacionNueva = new FileInputStream(ib.getRutaTemp());

            ObjectInputStream infoN = xstream.createObjectInputStream(informacionNueva);

            xstream.alias("tanque", Tanque.class);

            /**
             * Realizando el mapeo de un documento XML a un objeto Java
             */
            tanqueNuevo = (Tanque)infoN.readObject();

            infoN.close();
        } catch (IOException ex) {
            logger.error("Ocurrio un error de entrada/salida de datos");
            System.out.println("Ocurrió un error de entrada/salida de datos");
        } catch (ClassNotFoundException ex) {
            logger.error("Ocurrio un error de Clase No Encontrada");
            System.out.println("Ocurrió un error de Clase No Encontrada");
        } finally {
            return tanqueNuevo;
        }
    }

    /**
     * Método que actualiza el nivel de petróleo actualmente en el tanque
     * @param tanqueActual Tanque cuyo nivel de petróleo desea actualizarse
     * @param ruta Ruta donde será almacenada la información del tanque
     */
    public void actualizarNivel(Tanque tanqueActual) {
        InformacionBasica ib = obtenerInformacionBasica();

        try {
            /**
             * Instanciando un objeto de la clase que permite realizar el mapeo de Java a XML
             */
            XStream xstream = new XStream();

            /**
             * Declarando un flujo de salida de datos por archivo para actualizar la información
             * actual del tanque
             */
            FileOutputStream informacionTanque = new FileOutputStream(ib.getRuta());

            ObjectOutputStream infoT = xstream.createObjectOutputStream(informacionTanque);

            xstream.alias("tanque", Tanque.class);

            /**
             * Realizando el mapeo de un objeto Java a un documento XML
             */
            infoT.writeObject(tanqueActual);

            infoT.close();
        } catch (IOException ex) {
            logger.error("Ocurrio un error de entrada/salida de datos");
            System.out.println("Ocurrió un error de entrada/salida de datos");
        }
    }

    /**
     * Método que permite eliminar el contenido del archivo que contiene la información actual del
     * tanque
     */
    public void vaciarInformacionTanque() {
        InformacionBasica ib = obtenerInformacionBasica();

        try {
            BufferedWriter editor = new BufferedWriter(new FileWriter(ib.getRuta()));
            editor.write("");
            editor.close();
        } catch (IOException ex) {
            logger.error("Ocurrio un error de entrada/salida de datos");
            System.out.println("Ocurrió un error de entrada/salida de datos");
        }
    }

    /**
     * Método que permite eliminar el contenido del archivo que contiene la información temporal del
     * tanque actual
     */
    public void vaciarInformacionTemporalTanque() {
        InformacionBasica ib = obtenerInformacionBasica();

        try {
            BufferedWriter editor = new BufferedWriter(new FileWriter(ib.getRutaTemp()));
            editor.write("");
            editor.close();
        } catch (IOException ex) {
            logger.error("Ocurrio un error de entrada/salida de datos");
            System.out.println("Ocurrió un error de entrada/salida de datos");
        }
    }

    /**
     * Método que carga la información referente a las conexiones necesarias para el funcionamiento
     * de la aplicación
     * @return Devuelve un objeto Java con la información de rutas, puertos, ip, etc.
     */
    public InformacionBasica obtenerInformacionBasica() {
        InformacionBasica ib = new InformacionBasica();

        try {
            /**
             * Instanciando un objeto de la clase que permite realizar el mapeo de XML a Java
             */
            XStream xstream = new XStream();

            /**
             * Declarando un flujo de entrada de datos por archivo para obtener la información
             * actual del tanque
             */
            FileInputStream informacionBasica = new FileInputStream(rutaInformacionBasica);

            ObjectInputStream infoB = xstream.createObjectInputStream(informacionBasica);

            xstream.alias("InformacionBasica", InformacionBasica.class);

            /**
             * Realizando el mapeo de un documento XML a un objeto Java
             */
            ib = (InformacionBasica)infoB.readObject();

            infoB.close();
        } catch (IOException ex) {
            logger.error("Ocurrio un error de entrada/salida de datos");
            System.out.println("Ocurrió un error de entrada/salida de datos");
        } catch (ClassNotFoundException ex) {
            logger.error("Ocurrio un error de Clase No Encontrada");
            System.out.println("Ocurrió un error de Clase No Encontrada");
        } finally {
            return ib;
        }
    }

    /**
     * Método estático para emitir mensaje de colapso de sistema. Sólo para el uso del coordinador
     * @param mensaje Mensaje que envía el tanque no coordinador una vez se encuentre desbordado
     */
    public static boolean validarDesbordamiento(String mensaje) {
        InformacionBasica ib = (new Informacion()).obtenerInformacionBasica();
        boolean fin = false;

        int idTanque = 0;
        if (!mensaje.equals("false"))
            idTanque = Integer.parseInt(mensaje.substring(mensaje.indexOf(":")-1,
                    mensaje.indexOf(":")));

        String mensajeColapsoTanqueCoordinador = "";
        String mensajeColapso = "Colapso del sistema.\n";
   
        if (!mensaje.equals("false")) {
            if (ib.getIdTanque1() == idTanque) {
                Informacion.estadoDesbordamientoTanque1 = true;
                Informacion.mensajeColapsoTanque1 = mensaje;
            }

            if (ib.getIdTanque2() == idTanque) {
                Informacion.estadoDesbordamientoTanque2 = true;
                Informacion.mensajeColapsoTanque2 = mensaje;
            }

        }

        else if (ib.getIdTanque1() == idTanque)
            Informacion.estadoDesbordamientoTanque1 = false;

        else if (ib.getIdTanque2() == idTanque)
            Informacion.estadoDesbordamientoTanque2 = false;

        if (Informacion.estadoDesbordamientoTanque1 && Informacion.estadoDesbordamientoTanque2) {
            mensajeColapso += Informacion.mensajeColapsoTanque1 + "\n" +
                    Informacion.mensajeColapsoTanque2;

            Informacion i = new Informacion();
            Tanque tanqueActual = i.consultarNivelActual();

            if (tanqueActual.getLts() >= ib.getCapacidadTanque()) {
                try {
                    fin = true;

                    String ipLocal = InetAddress.getLocalHost().getHostAddress();

                    mensajeColapsoTanqueCoordinador = "Tanque " + ib.getIdTanque() + ": " + ipLocal
                            + ", capacidad: " + ib.getCapacidadTanque() + " litros, nivel actual: "
                            + tanqueActual.getLts() + " litros.";

                    mensajeColapso += "\n" + mensajeColapsoTanqueCoordinador;

                    EnvioMensajes msjColapso = new EnvioMensajes();
                    msjColapso.enviarMensajeColapso(mensajeColapso);

                    logger.error("Colapso del sistema.");
                    logger.error(mensajeColapsoTanque1);
                    logger.error(mensajeColapsoTanque2);
                    logger.error(mensajeColapsoTanqueCoordinador);

                    System.out.println(mensajeColapso);

                    System.exit(0);
                } catch (UnknownHostException ex) {
                    logger.error("El equipo no tiene una direccion ip asignada");
                    System.out.println("El equipo no tiene una dirección ip asignada");
                }
            }
        }

        return fin;
    }
}
